package cn.yangliu.jpa.handler;

import java.math.BigDecimal;
import java.sql.Timestamp;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;

import javax.persistence.EntityManagerFactory;
import javax.persistence.TypedQuery;
import javax.sql.DataSource;

import cn.yangliu.jpa.enums.DbTypeEnum;
import cn.yangliu.jpa.function.ParamTransformer;
import cn.yangliu.jpa.function.PredicateBuilder;
import cn.yangliu.jpa.function.ValueTransformer;
import cn.yangliu.jpa.tools.JpaUtils;
import org.apache.commons.lang.time.DateFormatUtils;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;

/**
 * The interface Param transformer.
 *
 * @author 问道于盲
 * @date 2019-06-14
 */
public class JpaHandler {

    /**
     * current class's static member
     * The constant INSTANCE.
     */
    public static final JpaHandler INSTANCE = new JpaHandler();


    /**
     * 参数转换器
     * 查询对象的成员属性为A类型
     * 但数据库支持的是B类型,则需要定义参数转换器
     * 此处为默认的一个参数转换器
     *
     * current class's static member
     * The constant PARAM_TRANSFORMER.
     */
    private static final ParamTransformer PARAM_TRANSFORMER = (column, fieldName, value) -> {

        if (Objects.equals(DbTypeEnum.ORACLE, JpaUtils.getDbType())) {
            if (value instanceof LocalDateTime) {
                value = Timestamp.valueOf((LocalDateTime) value);
            } else if (value instanceof LocalDate) {
                LocalDateTime temp = LocalDateTime.of((LocalDate) value, LocalTime.MIN);
                value = Timestamp.valueOf(temp);
            } else if (value instanceof Date) {
                value = Timestamp.valueOf(DateFormatUtils.format((Date) value, "yyyy-MM-dd HH:mm:sss"));
            } else if (value instanceof java.sql.Date) {
                value = Timestamp.valueOf(DateFormatUtils.format((Date) value, "yyyy-MM-dd HH:mm:sss"));
            }
        }
        return value;
    };

    /**
     * 数据库值转换器 用于sql查询到数据对象属性的转换
     * 如数据库厂商查询出来的列值类型为厂商驱动包类的某类型A或者jdk定义的其他类型
     * 但数据对象类型为B类型 需要自己定义转换流工具
     * 此处为默认的转换工具
     * The constant VALUE_TRANSFORMER.
     */
    private static final ValueTransformer VALUE_TRANSFORMER = (column, fieldType, value) -> {
        if (value instanceof Timestamp) {
            Timestamp timestamp = (Timestamp) value;
            if (Objects.equals(fieldType, LocalDateTime.class)) {
                value = timestamp.toInstant().atOffset(ZoneOffset.of("+8")).toLocalDateTime();
            } else if (Objects.equals(fieldType, LocalDate.class)) {
                value = timestamp.toInstant().atOffset(ZoneOffset.of("+8")).toLocalDate();
            } else if (Objects.equals(fieldType, LocalTime.class)) {
                value = timestamp.toInstant().atOffset(ZoneOffset.of("+8")).toLocalTime();
            } else if (Objects.equals(fieldType, Date.class)) {
                value = new Date(timestamp.getTime());
            } else if (Objects.equals(fieldType, java.sql.Date.class)) {
                value = new java.sql.Date(timestamp.getTime());
            }
        } else if (value instanceof BigDecimal) {
            BigDecimal decimal = (BigDecimal) value;
            if (Objects.equals(fieldType, Integer.class)) {
                value = decimal.intValue();
            }
            if (Objects.equals(fieldType, Long.class)) {
                value = decimal.longValue();
            }
            if (Objects.equals(fieldType, Float.class)) {
                value = decimal.floatValue();
            }
            if (Objects.equals(fieldType, Double.class)) {
                value = decimal.doubleValue();
            }
        }
        return value;

    };

    /**
     * Instantiates a new Jpa handler.
     * forbid other class to create a new instance.
     * only itself can create a new instance.
     */
    private JpaHandler() {
    }

    /**
     * current class method for setter.
     * Sets default db type.
     *
     * @param defaultDbType the default db type
     */
    public static void setDefaultDbType(DbTypeEnum defaultDbType) {
        JpaUtils.setDefaultdbtype(defaultDbType);
    }

    /**
     * current class method for setter.
     * Sets thread db type.
     *
     * @param dbType the db type
     */
    public static void setThreadDbType(DbTypeEnum dbType) {
        JpaUtils.THREAD_LOCAL.set(dbType);
    }

    /**
     * 构建Specification 查询条件
     *
     * @param <T>       实体类型
     * @param queryPojo 查询封装
     * @return Specification specification
     */
    public <T> Specification<T> buildSpecification(Object queryPojo) {
        return SpecificationHandler.buildSpecification(queryPojo);
    }

    /**
     * 构建Specification 查询条件
     *
     * @param <T>       实体类型
     * @param queryPojo 查询封装
     * @param builder   builder
     * @return Specification specification
     */
    public <T> Specification<T> buildSpecification(Object queryPojo, PredicateBuilder builder) {
        return SpecificationHandler.buildSpecification(queryPojo, builder);
    }

    /**
     * 通过动态sql 实现复杂查询并分页
     *
     * @param <T>        泛型
     * @param dataSource 数据源
     * @param queryPojo  查询条件封装
     * @param pageable   分页条件
     * @return list数据 page
     */
    public <T> Page<T> queryForPageBySql(DataSource dataSource, Object queryPojo, Pageable pageable) {
        return SqlHandler.queryForPageBySql(dataSource, queryPojo, pageable, PARAM_TRANSFORMER,
                VALUE_TRANSFORMER);
    }

    /**
     * 通过动态sql 实现复杂查询并分页
     *
     * @param <T>              泛型
     * @param dataSource       数据源
     * @param queryPojo        查询条件封装
     * @param pageable         分页条件
     * @param paramTransformer 参数转换器
     * @param valueTransformer 值转换器
     * @return list数据 page
     */
    public <T> Page<T> queryForPageBySql(DataSource dataSource, Object queryPojo, Pageable pageable,
                                         ParamTransformer paramTransformer, ValueTransformer valueTransformer) {

        if (Objects.isNull(paramTransformer)) {
            paramTransformer = PARAM_TRANSFORMER;
        }

        if (Objects.isNull(valueTransformer)) {
            valueTransformer = VALUE_TRANSFORMER;
        }
        return SqlHandler.queryForPageBySql(dataSource, queryPojo, pageable, paramTransformer,
                valueTransformer);
    }


    /**
     * 通过动态sql 实现复杂查询并分页
     *
     * @param <T>        泛型
     * @param dataSource 数据源
     * @param queryPojo  查询条件封装
     * @return list数据 page
     */
    public <T> List<T> queryForListBySql(DataSource dataSource, Object queryPojo) {
        return SqlHandler.queryForListBySql(dataSource, queryPojo, PARAM_TRANSFORMER, VALUE_TRANSFORMER);
    }

    /**
     * 通过动态sql 实现复杂查询并分页
     *
     * @param <T>              泛型
     * @param dataSource       数据源
     * @param queryPojo        查询条件封装
     * @param paramTransformer the param transformer
     * @param valueTransformer the value transformer
     * @return list数据 page
     */
    public <T> List<T> queryForListBySql(DataSource dataSource, Object queryPojo,
                                         ParamTransformer paramTransformer, ValueTransformer valueTransformer) {
        if (Objects.isNull(paramTransformer)) {
            paramTransformer = PARAM_TRANSFORMER;
        }

        if (Objects.isNull(valueTransformer)) {
            valueTransformer = VALUE_TRANSFORMER;
        }
        return SqlHandler.queryForListBySql(dataSource, queryPojo, paramTransformer,
                valueTransformer);
    }

    /**
     * 通过动态sql 实现复杂查询并映射
     *
     * @param <T>        泛型
     * @param dataSource 数据源
     * @param queryPojo  查询条件封装
     * @return 单条数据 t
     */
    public <T> T queryForObjectBySql(DataSource dataSource, Object queryPojo) {
        return SqlHandler.queryForObjectBySql(dataSource, queryPojo, PARAM_TRANSFORMER, VALUE_TRANSFORMER);
    }

    /**
     * 通过动态sql 实现复杂查询并映射
     *
     * @param <T>              泛型
     * @param dataSource       数据源
     * @param queryPojo        查询条件封装
     * @param paramTransformer 参数转换器
     * @param valueTransformer 结果转换器
     * @return 单条数据 t
     */
    public <T> T queryForObjectBySql(DataSource dataSource, Object queryPojo,
                                     ParamTransformer paramTransformer, ValueTransformer valueTransformer) {

        if (Objects.isNull(paramTransformer)) {
            paramTransformer = PARAM_TRANSFORMER;
        }

        if (Objects.isNull(valueTransformer)) {
            valueTransformer = VALUE_TRANSFORMER;
        }

        return SqlHandler.queryForObjectBySql(dataSource, queryPojo, paramTransformer,
                valueTransformer);
    }

    /**
     * 构建jpal查询对象
     *
     * @param <T>                  实体类型
     * @param entityManagerFactory entityManagerFactory
     * @param queryPojo            查询实体对象封装
     * @param sort                 排序信息封装
     * @return 查询对象 typed query
     */
    public <T> TypedQuery<T> buildJpqlQuery(EntityManagerFactory entityManagerFactory, Object queryPojo, Sort sort) {
        return JpqlHandler.buildJpqlQuery(entityManagerFactory, queryPojo, sort);
    }

    /**
     * 构建jpal查询对象
     *
     * @param <T>                  实体类型
     * @param entityManagerFactory entityManagerFactory
     * @param queryPojo            查询实体对象封装
     * @return 查询对象 typed query
     */
    public <T> TypedQuery<T> buildJpqlQuery(EntityManagerFactory entityManagerFactory, Object queryPojo) {
        return JpqlHandler.buildJpqlQuery(entityManagerFactory, queryPojo, Sort.unsorted());
    }

    /**
     * 构建jpql语句
     *
     * @param queryPojo 参数封装对象
     * @param params    外部传入的一个空list 用于临时存储参数
     * @param fields    外部传入的一个空list 用于临时存储属性名
     * @param orderBy   排序和分页
     * @return 完整的jpql string
     */
    public String buildJpql(Object queryPojo, List<Object> params, List<String> fields,
                            String orderBy) {
        String sourceJpql = JpaUtils.getSourceJpql(queryPojo);
        return JpqlHandler.buildJpql(sourceJpql, queryPojo, params, fields, orderBy);
    }

    /**
     * 构建jpql语句
     *
     * @param queryPojo 参数封装对象
     * @param params    外部传入的一个空list 用于临时存储参数
     * @param fields    外部传入的一个空list 用于临时存储属性名
     * @return 完整的jpql string
     */
    public String buildJpql(Object queryPojo, List<Object> params, List<String> fields) {
        String sourceJpql = JpaUtils.getSourceJpql(queryPojo);
        return JpqlHandler.buildJpql(sourceJpql, queryPojo, params, fields, null);
    }

    /**
     * 构建TypedQuery
     *
     * @param <T>                  泛型
     * @param entityManagerFactory entityManagerFactory
     * @param jpql                 jpql <from User where username = :username and email like :email>
     * @param clazz                目标对象类
     * @param params               参数list
     * @param fields               属性名
     * @return TypedQuery typed query
     */
    public <T> TypedQuery<T> buildQueryParameters(EntityManagerFactory entityManagerFactory, String jpql,
                                                  Class<T> clazz, List<Object> params, List<String> fields) {
        return JpqlHandler.buildQueryParameters(entityManagerFactory, jpql, clazz, params, fields);
    }

    /**
     * 构建TypedQuery
     *
     * @param <T>                  泛型
     * @param entityManagerFactory entityManagerFactory
     * @param jpql                 jpql <from User where username = :username and email like :email>
     * @param queryPojo            查询参数封装
     * @param params               参数list
     * @param fields               属性名
     * @return TypedQuery typed query
     */
    public <T> TypedQuery<T> buildQueryParameters(EntityManagerFactory entityManagerFactory, String jpql,
                                                  Object queryPojo, List<Object> params, List<String> fields) {
        return JpqlHandler.buildQueryParameters(entityManagerFactory, jpql, queryPojo, params, fields);
    }

    /**
     * 构建排序字符串
     *
     * @param pageable pageable
     * @return order string
     */
    public String buildOrderBy(Pageable pageable) {
        Sort sort = pageable.getSort();
        List<String> orders = new ArrayList<>();
        List<String> directions = new ArrayList<>();
        JpaUtils.buildOrders(sort, orders, directions);
        return JpaUtils.buildOrderBy(orders, directions);
    }

    /**
     * 设置全局数据库类型 默认mysql
     *
     * @param globalDbType globalDbType
     */
    public static void setGlobalDbType(DbTypeEnum globalDbType) {
        JpaUtils.setGlobalDbType(globalDbType);
    }
}
